﻿
/*
 * Copyright (c) 2012 Jeremy C. Brooks
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
 * files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 
 * modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 
 * is furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

using System;
using System.IO;
using System.Diagnostics;
using System.ServiceProcess;
using System.ComponentModel;
using System.Configuration.Install;
using System.Management;
using System.Collections.Generic;

namespace Servicize
{
    public static class Extensions
    {
        public static bool Contains(this List<Process> processes, int pid)
        {
            foreach (Process p in processes)
            {
                if (p.Id == pid)
                {
                    return true;
                }
            }
            return false;
        }
    }

    [RunInstaller(true)]
    public class ServicizeServiceInstaller : Installer
    {
        public ServicizeServiceInstaller()
        {
            var processInstaller = new ServiceProcessInstaller();
            var serviceInstaller = new ServiceInstaller();

            //set the privileges
            processInstaller.Account = ServiceAccount.LocalSystem;

            serviceInstaller.DisplayName = "Servicize";
            serviceInstaller.StartType = ServiceStartMode.Manual;

            serviceInstaller.ServiceName = serviceInstaller.DisplayName;

            this.Installers.Add(processInstaller);
            this.Installers.Add(serviceInstaller);
        }
    }

    class Program : ServiceBase
    {
        private List<Process> processes = new List<Process>();
        private static string configFile = "C:\\Servicize\\config.txt";

        static void Main(string[] args)
        {
            Console.WriteLine("This program is meant to be run as a Windows service. It will allow you run a batch file, cmd or executable as a Windows service. Install using InstallUtil.exe or sc.");
            Console.WriteLine("The service will look for a config file located at:");
            Console.WriteLine(configFile);
            Console.WriteLine("The config file should contain the full path to the batch/exe/cmd file to be run as a service.");
            Console.WriteLine("Multiple commands can be run at once, just make sure each is on its own line.");
            ServiceBase.Run(new Program());
        }

        public Program()
        {
            this.ServiceName = "Servicize";
        }

        protected override void OnStart(string[] args)
        {
            if (!EventLog.SourceExists(ServiceName))
                EventLog.CreateEventSource(ServiceName, "Application");

            base.OnStart(args);

            processes.Clear();
            try
            {
                if (File.Exists(configFile))
                {
                    using (StreamReader sr = new StreamReader(File.Open(configFile, FileMode.Open)))
                    {
                        while (!sr.EndOfStream)
                        {
                            string exeFile = sr.ReadLine();
                            if (File.Exists(exeFile))
                            {
                                Process process = new Process();
                                process.StartInfo.FileName = exeFile;
                                process.StartInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(exeFile);
                                processes.Add(process);
                            }
                            else
                            {
                                EventLog.WriteEntry(ServiceName, "Could not start " + exeFile, EventLogEntryType.Error);
                            }
                        }
                        sr.Close();
                    }
                }

                foreach (Process process in processes)
                {
                    process.Start();
                }
            }
            catch (Exception ex)
            {
                EventLog.WriteEntry(ServiceName, "OnStart Failed." + Environment.NewLine + ex.Message, EventLogEntryType.Error);
            }
        }

        private List<Process> GetChildProcesses(Process parent)
        {
            List<Process> children = new List<Process>();

            Process[] processes = Process.GetProcesses();
            foreach (Process p in processes)
            {
                int parentPid = 0;
                using (ManagementObject mo = new ManagementObject("win32_process.handle='" + p.Id + "'"))
                {
                    mo.Get();
                    parentPid = Convert.ToInt32(mo["ParentProcessId"]);
                }

                if (parentPid == parent.Id)
                {
                    children.Add(p);
                }
            }
            return children;
        }

        private void KillProcessAndChildren(Process parent)
        {
            List<Process> processesToKill = GetChildProcesses(parent);
            foreach (Process p in processesToKill)
            {
                KillProcessAndChildren(p);
            }

            try
            {
                parent.Kill();
            }
            catch (Exception ex)
            {
                if (!processes.Contains(parent.Id))
                {
                    EventLog.WriteEntry(ServiceName, "KillProcessAndChildren: Failed to kill " + parent.Id + Environment.NewLine + ex.Message, EventLogEntryType.Error);
                }
            }
        }

        protected override void OnStop()
        {
            base.OnStop();

            try
            {
                foreach (Process process in processes)
                {
                    KillProcessAndChildren(process);
                }
            }
            catch (Exception ex)
            {
                EventLog.WriteEntry(ServiceName, "OnStop: Failed to stop." + Environment.NewLine + ex.Message, EventLogEntryType.Error);
            }

            processes.Clear();
        }
    }
}
